/*
 *                 [[ Frozen-Bubble ]]
 *
 * Copyright (c) 2000-2003 Guillaume Cottenceau.
 * Java sourcecode - Copyright (c) 2003 Glenn Sanson.
 *
 * This code is distributed under the GNU General Public License
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 * Artwork:
 *    Alexis Younes <73lab at free.fr>
 *      (everything but the bubbles)
 *    Amaury Amblard-Ladurantie <amaury at linuxfr.org>
 *      (the bubbles)
 *
 * Soundtrack:
 *    Matthias Le Bidan <matthias.le_bidan at caramail.com>
 *      (the three musics and all the sound effects)
 *
 * Design & Programming:
 *    Guillaume Cottenceau <guillaume.cottenceau at free.fr>
 *      (design and manage the project, whole Perl sourcecode)
 *
 * Java version:
 *    Glenn Sanson <glenn.sanson at free.fr>
 *      (whole Java sourcecode, including JIGA classes
 *             http://glenn.sanson.free.fr/jiga/)
 *
 * Android port:
 *    Pawel Aleksander Fedorynski <pfedor@fuw.edu.pl>
 *    Copyright (c) Google Inc.
 *
 *          [[ http://glenn.sanson.free.fr/fb/ ]]
 *          [[ http://www.frozen-bubble.org/   ]]
 */
// This file is derived from the LunarView.java file which is part of
// the Lunar Lander game included with Android documentation.  The copyright
// notice for the Lunar Lander is reproduced below.
/*
 * Copyright (C) 2007 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package com.example.lianliankdemo;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import org.jfedor.frozenbubble.R;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Vector;

/**
 * 游戏界面类SurfaceView
 */
class GameView extends SurfaceView implements SurfaceHolder.Callback {
    public class GameThread extends Thread {
        /**
         * 丢帧延迟
         */
        private static final int FRAME_DELAY = 40;

        /**
         * 状态：正在游戏中
         */
        public static final int STATE_RUNNING = 1;
        /**
         * 状态：暂停
         */
        public static final int STATE_PAUSE = 2;
        /**
         * 状态：关于泡泡龙
         */
        public static final int STATE_ABOUT = 4;

        //这两长宽要结合背景圖（320*480为游戏的内容区域）
        public static final int GAMEFIELD_WIDTH = 320;
        public static final int GAMEFIELD_HEIGHT = 480;
        //背景图宽度
        public static final int EXTENDED_GAMEFIELD_WIDTH = 640;

        private static final double TRACKBALL_COEFFICIENT = 5;
        /**
         * 点击发射小球的y阈值(当onTouch中y<380是可发射小球)
         */
        private static final double TOUCH_FIRE_Y_THRESHOLD = 380;
        private static final double ATS_TOUCH_COEFFICIENT = 0.2;
        /**
         * 瞄准后发射小球的y阈值(当onTouch中y<380是可发射小球)
         */
        private static final double ATS_TOUCH_FIRE_Y_THRESHOLD = 350;

        private int mCanvasHeight = 1;
        private int mCanvasWidth = 1;
        /**
         * 上次绘图的时间
         * 可以参考surfaceview刷新操作的一些优化建议:http://blog.csdn.net/lzq08ms/article/details/45717021
         */
        private long mLastTime;
        private int mMode;
        private boolean mRun = false;

        /**
         * 按下遥控左键
         */
        private boolean mLeft = false;
        /**
         * 按下遥控右键
         */
        private boolean mRight = false;
        /**
         * 按下遥控上键
         */
        private boolean mUp = false;
        /**
         * 按下遥控确定键(CENTER)
         */
        private boolean mFire = false;

        private boolean mWasLeft = false;
        private boolean mWasRight = false;
        private boolean mWasFire = false;
        private boolean mWasUp = false;
        private double mTrackballDX = 0;
        /**
         * 标志是否可以发射(点击发射的時候)
         */
        private boolean mTouchFire = false;
        /**
         * 触屏事件x坐标(点击发射)
         */
        private double mTouchX;
        /**
         * 触屏事件y坐标(点击发射)
         */
        private double mTouchY;
        /**
         * 标志是否可以发射(点击发射的時候)
         */
        private boolean mATSTouchFire = false;
        /**
         * 触屏事件y坐标(点击发射)
         */
        private double mATSTouchDX = 0;
        /**
         * 触屏事件y坐标(瞄准发射)
         */
        private double mATSTouchLastX;

        private SurfaceHolder mSurfaceHolder;
        private boolean mSurfaceOK = false;

        //（320*480）适应屏幕需要的放大倍数
        private double mDisplayScale;
        /**
         * 背景图片适配屏幕后左边偏移值
         */
        private int mDisplayDX;
        /**
         * 背景图片适配屏幕后右边偏移值
         */
        private int mDisplayDY;

        private FrozenGame mFrozenGame;

        private boolean mImagesReady = false;

        private Bitmap mBackgroundOrig;
        /**
         * 正常模式的精灵背景
         */
        private Bitmap[] mBubblesOrig;
        /**
         * 色盲模式的精灵背景
         */
        private Bitmap[] mBubblesBlindOrig;
        /**
         * 失败的时候冰冻住的精灵的背景
         */
        private Bitmap[] mFrozenBubblesOrig;
        /**
         * 发射中精灵粘住的时候闪烁的的精灵背景
         */
        private Bitmap[] mTargetedBubblesOrig;
        private Bitmap mBubbleBlinkOrig;
        private Bitmap mGameWonOrig;
        private Bitmap mGameLostOrig;
        private Bitmap mHurryOrig;
        private Bitmap mPenguinsOrig;
        private Bitmap mCompressorHeadOrig;
        private Bitmap mCompressorOrig;
        private Bitmap mLifeOrig;
        private Bitmap mFontImageOrig;
        private BmpWrap mBackground;
        /**
         * 正常模式的精灵包装器
         */
        private BmpWrap[] mBubbles;
        /**
         * 色盲模式的精灵包装器
         */
        private BmpWrap[] mBubblesBlind;
        /**
         * 失败的时候冰冻住的精灵的包装器
         */
        private BmpWrap[] mFrozenBubbles;
        /**
         * 发射中精灵粘住的时候闪烁的精灵的包装器
         */
        private BmpWrap[] mTargetedBubbles;
        /**
         * 闪烁背景(整体闪烁的时候的背景)
         */
        private BmpWrap mBubbleBlink;
        /**
         * 胜利背景
         */
        private BmpWrap mGameWon;
        /**
         * 失败背景
         */
        private BmpWrap mGameLost;
        /**
         * 催促背景
         */
        private BmpWrap mHurry;
        /**
         * 企鹅背景
         */
        private BmpWrap mPenguins;
        /**
         * 发射机背景????
         */
        private BmpWrap mCompressorHead;
        private BmpWrap mCompressor;
        private BmpWrap mLife;
        private BmpWrap mFontImage;
        // Launcher has to be a drawable, not a bitmap, because we rotate it.
        /**
         * 发射架上的指示器
         */
        private Drawable mLauncher;
        private SoundManager mSoundManager;
        private LevelManager mLevelManager;
        /**
         * 字体背景
         */
        private BubbleFont mFont;

        Vector mImageList;

        public int getCurrentLevelIndex() {
            synchronized (mSurfaceHolder) {
                return mLevelManager.getLevelIndex();
            }
        }

        /**
         * 依次添加图片到向量，并放回最后一个
         *
         * @return
         */
        private BmpWrap NewBmpWrap() {
            int new_img_id = mImageList.size();
            BmpWrap new_img = new BmpWrap(new_img_id);
            mImageList.addElement(new_img);
            return new_img;
        }

        public GameThread(SurfaceHolder surfaceHolder, byte[] customLevels,
                          int startingLevel) {
            //Log.i("frozen-bubble", "GameThread()");
            mSurfaceHolder = surfaceHolder;
            Resources res = mContext.getResources();
            setState(STATE_PAUSE);

            BitmapFactory.Options options = new BitmapFactory.Options();

            // The Options.inScaled field is only available starting at API 4.
            try {
                Field f = options.getClass().getField("inScaled");
                f.set(options, Boolean.FALSE);
            } catch (Exception ignore) {
            }

            mBackgroundOrig =
                    BitmapFactory.decodeResource(res, R.drawable.background, options);
            mBubblesOrig = new Bitmap[8];
            mBubblesOrig[0] = BitmapFactory.decodeResource(res, R.drawable.bubble_1,
                    options);
            mBubblesOrig[1] = BitmapFactory.decodeResource(res, R.drawable.bubble_2,
                    options);
            mBubblesOrig[2] = BitmapFactory.decodeResource(res, R.drawable.bubble_3,
                    options);
            mBubblesOrig[3] = BitmapFactory.decodeResource(res, R.drawable.bubble_4,
                    options);
            mBubblesOrig[4] = BitmapFactory.decodeResource(res, R.drawable.bubble_5,
                    options);
            mBubblesOrig[5] = BitmapFactory.decodeResource(res, R.drawable.bubble_6,
                    options);
            mBubblesOrig[6] = BitmapFactory.decodeResource(res, R.drawable.bubble_7,
                    options);
            mBubblesOrig[7] = BitmapFactory.decodeResource(res, R.drawable.bubble_8,
                    options);
            mBubblesBlindOrig = new Bitmap[8];
            mBubblesBlindOrig[0] = BitmapFactory.decodeResource(
                    res, R.drawable.bubble_colourblind_1, options);
            mBubblesBlindOrig[1] = BitmapFactory.decodeResource(
                    res, R.drawable.bubble_colourblind_2, options);
            mBubblesBlindOrig[2] = BitmapFactory.decodeResource(
                    res, R.drawable.bubble_colourblind_3, options);
            mBubblesBlindOrig[3] = BitmapFactory.decodeResource(
                    res, R.drawable.bubble_colourblind_4, options);
            mBubblesBlindOrig[4] = BitmapFactory.decodeResource(
                    res, R.drawable.bubble_colourblind_5, options);
            mBubblesBlindOrig[5] = BitmapFactory.decodeResource(
                    res, R.drawable.bubble_colourblind_6, options);
            mBubblesBlindOrig[6] = BitmapFactory.decodeResource(
                    res, R.drawable.bubble_colourblind_7, options);
            mBubblesBlindOrig[7] = BitmapFactory.decodeResource(
                    res, R.drawable.bubble_colourblind_8, options);
            mFrozenBubblesOrig = new Bitmap[8];
            mFrozenBubblesOrig[0] = BitmapFactory.decodeResource(
                    res, R.drawable.frozen_1, options);
            mFrozenBubblesOrig[1] = BitmapFactory.decodeResource(
                    res, R.drawable.frozen_2, options);
            mFrozenBubblesOrig[2] = BitmapFactory.decodeResource(
                    res, R.drawable.frozen_3, options);
            mFrozenBubblesOrig[3] = BitmapFactory.decodeResource(
                    res, R.drawable.frozen_4, options);
            mFrozenBubblesOrig[4] = BitmapFactory.decodeResource(
                    res, R.drawable.frozen_5, options);
            mFrozenBubblesOrig[5] = BitmapFactory.decodeResource(
                    res, R.drawable.frozen_6, options);
            mFrozenBubblesOrig[6] = BitmapFactory.decodeResource(
                    res, R.drawable.frozen_7, options);
            mFrozenBubblesOrig[7] = BitmapFactory.decodeResource(
                    res, R.drawable.frozen_8, options);
            mTargetedBubblesOrig = new Bitmap[6];
            mTargetedBubblesOrig[0] = BitmapFactory.decodeResource(
                    res, R.drawable.fixed_1, options);
            mTargetedBubblesOrig[1] = BitmapFactory.decodeResource(
                    res, R.drawable.fixed_2, options);
            mTargetedBubblesOrig[2] = BitmapFactory.decodeResource(
                    res, R.drawable.fixed_3, options);
            mTargetedBubblesOrig[3] = BitmapFactory.decodeResource(
                    res, R.drawable.fixed_4, options);
            mTargetedBubblesOrig[4] = BitmapFactory.decodeResource(
                    res, R.drawable.fixed_5, options);
            mTargetedBubblesOrig[5] = BitmapFactory.decodeResource(
                    res, R.drawable.fixed_6, options);
            mBubbleBlinkOrig =
                    BitmapFactory.decodeResource(res, R.drawable.bubble_blink, options);
            mGameWonOrig = BitmapFactory.decodeResource(res, R.drawable.win_panel,
                    options);
            mGameLostOrig = BitmapFactory.decodeResource(res, R.drawable.lose_panel,
                    options);
            mHurryOrig = BitmapFactory.decodeResource(res, R.drawable.hurry, options);
            mPenguinsOrig = BitmapFactory.decodeResource(res, R.drawable.penguins,
                    options);
            mCompressorHeadOrig =
                    BitmapFactory.decodeResource(res, R.drawable.compressor, options);
            mCompressorOrig =
                    BitmapFactory.decodeResource(res, R.drawable.compressor_body, options);
            mLifeOrig = BitmapFactory.decodeResource(res, R.drawable.life, options);
            mFontImageOrig =
                    BitmapFactory.decodeResource(res, R.drawable.bubble_font, options);

            mImageList = new Vector();

            mBackground = NewBmpWrap();
            mBubbles = new BmpWrap[8];
            for (int i = 0; i < mBubbles.length; i++) {
                mBubbles[i] = NewBmpWrap();
            }
            mBubblesBlind = new BmpWrap[8];
            for (int i = 0; i < mBubblesBlind.length; i++) {
                mBubblesBlind[i] = NewBmpWrap();
            }
            mFrozenBubbles = new BmpWrap[8];
            for (int i = 0; i < mFrozenBubbles.length; i++) {
                mFrozenBubbles[i] = NewBmpWrap();
            }
            mTargetedBubbles = new BmpWrap[6];
            for (int i = 0; i < mTargetedBubbles.length; i++) {
                mTargetedBubbles[i] = NewBmpWrap();
            }
            mBubbleBlink = NewBmpWrap();
            mGameWon = NewBmpWrap();
            mGameLost = NewBmpWrap();
            mHurry = NewBmpWrap();
            mPenguins = NewBmpWrap();
            mCompressorHead = NewBmpWrap();
            mCompressor = NewBmpWrap();
            mLife = NewBmpWrap();
            mFontImage = NewBmpWrap();

            mFont = new BubbleFont(mFontImage);
            mLauncher = res.getDrawable(R.drawable.launcher);

            mSoundManager = new SoundManager(mContext);

            if (null == customLevels) {
                try {
                    InputStream is = mContext.getAssets().open("levels.txt");
                    int size = is.available();
                    byte[] levels = new byte[size];
                    is.read(levels);
                    is.close();
                    SharedPreferences sp = mContext.getSharedPreferences(
                            FrozenBubble.PREFS_NAME, Context.MODE_PRIVATE);
                    startingLevel = sp.getInt("level", 0);
                    mLevelManager = new LevelManager(levels, startingLevel);
                } catch (IOException e) {
                    // Should never happen.
                    throw new RuntimeException(e);
                }
            } else {
                // We were launched by the level editor.
                mLevelManager = new LevelManager(customLevels, startingLevel);
            }

            mFrozenGame = new FrozenGame(mBackground, mBubbles, mBubblesBlind,
                    mFrozenBubbles, mTargetedBubbles,
                    mBubbleBlink, mGameWon, mGameLost,
                    mHurry, mPenguins, mCompressorHead,
                    mCompressor, mLauncher,
                    mSoundManager, mLevelManager);
        }

        /**
         * 截取图片
         *
         * @param image
         * @param bmp
         */
        private void scaleFrom(BmpWrap image, Bitmap bmp) {
            if (image.bmp != null && image.bmp != bmp) {
                image.bmp.recycle();
            }

            if (mDisplayScale > 0.99999 && mDisplayScale < 1.00001) {
                image.bmp = bmp;
                return;
            }
            int dstWidth = (int) (bmp.getWidth() * mDisplayScale);
            int dstHeight = (int) (bmp.getHeight() * mDisplayScale);
            image.bmp = Bitmap.createScaledBitmap(bmp, dstWidth, dstHeight, true);
        }

        /**
         * 缩放各类图片资源，适应屏幕(各类分辨率)
         */
        private void resizeBitmaps() {
            //Log.i("frozen-bubble", "resizeBitmaps()");
            scaleFrom(mBackground, mBackgroundOrig);
            for (int i = 0; i < mBubblesOrig.length; i++) {
                scaleFrom(mBubbles[i], mBubblesOrig[i]);
            }
            for (int i = 0; i < mBubblesBlind.length; i++) {
                scaleFrom(mBubblesBlind[i], mBubblesBlindOrig[i]);
            }
            for (int i = 0; i < mFrozenBubbles.length; i++) {
                scaleFrom(mFrozenBubbles[i], mFrozenBubblesOrig[i]);
            }
            for (int i = 0; i < mTargetedBubbles.length; i++) {
                scaleFrom(mTargetedBubbles[i], mTargetedBubblesOrig[i]);
            }
            scaleFrom(mBubbleBlink, mBubbleBlinkOrig);
            scaleFrom(mGameWon, mGameWonOrig);
            scaleFrom(mGameLost, mGameLostOrig);
            scaleFrom(mHurry, mHurryOrig);
            scaleFrom(mPenguins, mPenguinsOrig);
            scaleFrom(mCompressorHead, mCompressorHeadOrig);
            scaleFrom(mCompressor, mCompressorOrig);
            scaleFrom(mLife, mLifeOrig);
            scaleFrom(mFontImage, mFontImageOrig);
            //Log.i("frozen-bubble", "resizeBitmaps done.");
            mImagesReady = true;
        }


        public void pause() {
            synchronized (mSurfaceHolder) {
                if (mMode == STATE_RUNNING) {
                    setState(STATE_PAUSE);
                }
            }
        }

        public void newGame() {
            synchronized (mSurfaceHolder) {
                mLevelManager.goToFirstLevel();
                mFrozenGame = new FrozenGame(mBackground, mBubbles, mBubblesBlind,
                        mFrozenBubbles, mTargetedBubbles,
                        mBubbleBlink, mGameWon, mGameLost,
                        mHurry, mPenguins, mCompressorHead,
                        mCompressor, mLauncher,
                        mSoundManager, mLevelManager);
            }
        }


        @Override
        public void run() {
            while (mRun) {
                long now = System.currentTimeMillis();
                //一般来说使用surfaceview定时刷新时，时常会通过新线程循环绘制，然后在调用Thread.sleep(int)来暂停线程达到定时刷新的效果
                //通过记录操作时间，来消除不必要的等待
                long delay = FRAME_DELAY + mLastTime - now;
                if (delay > 0) {
                    try {
                        sleep(delay);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                mLastTime = now;
                Canvas c = null;
                try {
                    if (surfaceOK()) {
                        c = mSurfaceHolder.lockCanvas(null);
                        if (c != null) {
                            synchronized (mSurfaceHolder) {
                                if (mRun) {
                                    if (mMode == STATE_ABOUT) {
                                        drawAboutScreen(c);
                                    } else {
                                        if (mMode == STATE_RUNNING) {
                                            updateGameState();
                                        }
                                        //绘图
                                        doDraw(c);
                                    }
                                }
                            }
                        }
                    }
                } finally {
                    // do this in a finally so that if an exception is thrown
                    // during the above, we don't leave the Surface in an
                    // inconsistent state
                    if (c != null) {
                        mSurfaceHolder.unlockCanvasAndPost(c);
                    }
                }
            }
        }

        /**
         * Dump game state to the provided Bundle. Typically called when the
         * Activity is being suspended.
         *
         * @return Bundle with this view's state
         */
        public Bundle saveState(Bundle map) {
            synchronized (mSurfaceHolder) {
                if (map != null) {
                    mFrozenGame.saveState(map);
                    mLevelManager.saveState(map);
                }
            }
            return map;
        }

        /**
         * Restores game state from the indicated Bundle. Typically called when
         * the Activity is being restored after having been previously
         * destroyed.
         *
         * @param map Bundle containing the game state
         */
        public synchronized void restoreState(Bundle map) {
            synchronized (mSurfaceHolder) {
                setState(STATE_PAUSE);
                mFrozenGame.restoreState(map, mImageList);
                mLevelManager.restoreState(map);
            }
        }

        public void setRunning(boolean b) {
            mRun = b;
        }

        public void setState(int mode) {
            synchronized (mSurfaceHolder) {
                mMode = mode;
            }
        }

        /**
         * surfaceview 调用 surfaceCreated的时候触发 true
         * surfaceview 调用 surfaceDestroyed的时候触发 false
         *
         * @param ok
         */
        public void setSurfaceOK(boolean ok) {
            synchronized (mSurfaceHolder) {
                mSurfaceOK = ok;
            }
        }

        public boolean surfaceOK() {
            synchronized (mSurfaceHolder) {
                return mSurfaceOK;
            }
        }

        /**
         * 这个方法的逻辑很重要，关系到精灵的准确位置
         *
         * @param width
         * @param height
         */
        public void setSurfaceSize(int width, int height) {
            synchronized (mSurfaceHolder) {
                mCanvasWidth = width;
                mCanvasHeight = height;
                if (width / height >= GAMEFIELD_WIDTH / GAMEFIELD_HEIGHT) {
                    mDisplayScale = 1.0 * height / GAMEFIELD_HEIGHT;//高度不变，长度按320/480截取???
                    //背景图按比例放大后减去设备宽度后的一半，
                    //也就是背景图放大后左边距屏幕左边的距离，
                    //画精灵的时候要考虑这部分的偏移
                    mDisplayDX = (int) ((width - mDisplayScale * EXTENDED_GAMEFIELD_WIDTH) / 2);
                    mDisplayDY = 0;
                } else {
                    mDisplayScale = 1.0 * width / GAMEFIELD_WIDTH;
                    mDisplayDX = (int) (-mDisplayScale *
                            (EXTENDED_GAMEFIELD_WIDTH - GAMEFIELD_WIDTH) / 2);
                    mDisplayDY = (int) ((height - mDisplayScale * GAMEFIELD_HEIGHT) / 2);
                }
                resizeBitmaps();
            }
        }

        boolean doKeyDown(int keyCode, KeyEvent msg) {
            synchronized (mSurfaceHolder) {
                if (mMode != STATE_RUNNING) {
                    setState(STATE_RUNNING);
                }

                if (mMode == STATE_RUNNING) {
                    //Log.i("frozen-bubble", "STATE RUNNING");
                    if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
                        mLeft = true;
                        mWasLeft = true;
                        return true;
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
                        mRight = true;
                        mWasRight = true;
                        return true;
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
                        mFire = true;
                        mWasFire = true;
                        return true;
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                        mUp = true;
                        mWasUp = true;
                        return true;
                    }
                }

                return false;
            }
        }

        boolean doKeyUp(int keyCode, KeyEvent msg) {
            synchronized (mSurfaceHolder) {
                if (mMode == STATE_RUNNING) {
                    if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
                        mLeft = false;
                        return true;
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
                        mRight = false;
                        return true;
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
                        mFire = false;
                        return true;
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                        mUp = false;
                        return true;
                    }
                }
                return false;
            }
        }

        /**
         * 当用户在该组件上触发轨迹球屏事件时触发该方法。(现在的手机基本上没有轨迹球了)
         *
         * @param event
         * @return
         */
        boolean doTrackballEvent(MotionEvent event) {
            synchronized (mSurfaceHolder) {
                if (mMode != STATE_RUNNING) {
                    setState(STATE_RUNNING);
                }

                if (mMode == STATE_RUNNING) {
                    if (event.getAction() == MotionEvent.ACTION_MOVE) {
                        mTrackballDX += event.getX() * TRACKBALL_COEFFICIENT;
                        return true;
                    }
                }
                return false;
            }
        }

        private double xFromScr(float x) {
            return (x - mDisplayDX) / mDisplayScale;
        }

        private double yFromScr(float y) {
            return (y - mDisplayDY) / mDisplayScale;
        }

        boolean doTouchEvent(MotionEvent event) {
            synchronized (mSurfaceHolder) {
                if (mMode != STATE_RUNNING) {
                    setState(STATE_RUNNING);
                }

                double x = xFromScr(event.getX());
                double y = yFromScr(event.getY());

                // Set the values used when Point To Shoot is on.
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
//                if (event.getAction() == MotionEvent.ACTION_UP) {
                    if (y < TOUCH_FIRE_Y_THRESHOLD) {
                        mTouchFire = true;
                        mTouchX = x;
                        mTouchY = y;
                    }
                }

                // Set the values used when Aim Then Shoot is on.
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
//                if (event.getAction() == MotionEvent.ACTION_UP) {
                    if (y < ATS_TOUCH_FIRE_Y_THRESHOLD) {
                        mATSTouchFire = true;
                    }
                    mATSTouchLastX = x;
                } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
                    if (y >= ATS_TOUCH_FIRE_Y_THRESHOLD) {
                        mATSTouchDX = (x - mATSTouchLastX) * ATS_TOUCH_COEFFICIENT;
                    }
                    mATSTouchLastX = x;
                }

                return true;
            }
        }

        /**
         * 画背景图片
         *
         * @param c
         */
        private void drawBackground(Canvas c) {
            Sprite.drawImage(mBackground, 0, 0, c, mDisplayScale,
                    mDisplayDX, mDisplayDY);
        }

        /**
         * 画通关级别
         *
         * @param canvas
         */
        private void drawLevelNumber(Canvas canvas) {
            int y = 433;
            int x;
            int level = mLevelManager.getLevelIndex() + 1;
            if (level < 10) {
                x = 185;
                mFont.paintChar(Character.forDigit(level, 10), x, y, canvas,
                        mDisplayScale, mDisplayDX, mDisplayDY);
            } else if (level < 100) {
                x = 178;
                x += mFont.paintChar(Character.forDigit(level / 10, 10), x, y, canvas,
                        mDisplayScale, mDisplayDX, mDisplayDY);
                mFont.paintChar(Character.forDigit(level % 10, 10), x, y, canvas,
                        mDisplayScale, mDisplayDX, mDisplayDY);
            } else {
                x = 173;
                x += mFont.paintChar(Character.forDigit(level / 100, 10), x, y, canvas,
                        mDisplayScale, mDisplayDX, mDisplayDY);
                level -= 100 * (level / 100);
                x += mFont.paintChar(Character.forDigit(level / 10, 10), x, y, canvas,
                        mDisplayScale, mDisplayDX, mDisplayDY);
                mFont.paintChar(Character.forDigit(level % 10, 10), x, y, canvas,
                        mDisplayScale, mDisplayDX, mDisplayDY);
            }
        }

        /**
         * 画关于泡泡龙界面
         *
         * @param canvas
         */
        private void drawAboutScreen(Canvas canvas) {
            canvas.drawRGB(0, 0, 0);
            int x = 168;
            int y = 20;
            int ysp = 26;
            int indent = 10;
            mFont.print("original frozen bubble:", x, y, canvas,
                    mDisplayScale, mDisplayDX, mDisplayDY);
            y += ysp;
            mFont.print("guillaume cottenceau", x + indent, y, canvas,
                    mDisplayScale, mDisplayDX, mDisplayDY);
            y += ysp;
            mFont.print("alexis younes", x + indent, y, canvas,
                    mDisplayScale, mDisplayDX, mDisplayDY);
            y += ysp;
            mFont.print("amaury amblard-ladurantie", x + indent, y, canvas,
                    mDisplayScale, mDisplayDX, mDisplayDY);
            y += ysp;
            mFont.print("matthias le bidan", x + indent, y, canvas,
                    mDisplayScale, mDisplayDX, mDisplayDY);
            y += ysp;
            y += ysp;
            mFont.print("java version:", x, y, canvas,
                    mDisplayScale, mDisplayDX, mDisplayDY);
            y += ysp;
            mFont.print("glenn sanson", x + indent, y, canvas,
                    mDisplayScale, mDisplayDX, mDisplayDY);
            y += ysp;
            y += ysp;
            mFont.print("android port:", x, y, canvas,
                    mDisplayScale, mDisplayDX, mDisplayDY);
            y += ysp;
            mFont.print("aleksander fedorynski", x + indent, y, canvas,
                    mDisplayScale, mDisplayDX, mDisplayDY);
            y += 2 * ysp;
            mFont.print("android port source code", x, y, canvas,
                    mDisplayScale, mDisplayDX, mDisplayDY);
            y += ysp;
            mFont.print("is available at:", x, y, canvas,
                    mDisplayScale, mDisplayDX, mDisplayDY);
            y += ysp;
            mFont.print("http://code.google.com", x, y, canvas,
                    mDisplayScale, mDisplayDX, mDisplayDY);
            y += ysp;
            mFont.print("/p/frozenbubbleandroid", x, y, canvas,
                    mDisplayScale, mDisplayDX, mDisplayDY);
        }

        /**
         * @param canvas
         */
        private void doDraw(Canvas canvas) {
            //Log.i("frozen-bubble", "doDraw()");
            if (!mImagesReady) {
                //Log.i("frozen-bubble", "!mImagesReady, returning");
                return;
            }
            if (mDisplayDX > 0 || mDisplayDY > 0) {
                //Log.i("frozen-bubble", "Drawing black background.");
                canvas.drawRGB(0, 0, 0);
            }
            drawBackground(canvas);
            drawLevelNumber(canvas);
            mFrozenGame.paint(canvas, mDisplayScale, mDisplayDX, mDisplayDY);
        }

        /**
         * 更新游戏状态,但是不直接绘制精灵,绘图请看{@link #doDraw(Canvas)}
         */
        private void updateGameState() {
            if (mFrozenGame.play(mLeft || mWasLeft, mRight || mWasRight,
                    mFire || mUp || mWasFire || mWasUp,
                    mTrackballDX,
                    mTouchFire, mTouchX, mTouchY,
                    mATSTouchFire, mATSTouchDX)) {
                // Lost or won.  Need to start over.  The level is already
                // incremented if this was a win.
                mFrozenGame = new FrozenGame(mBackground, mBubbles, mBubblesBlind,
                        mFrozenBubbles, mTargetedBubbles,
                        mBubbleBlink, mGameWon, mGameLost,
                        mHurry, mPenguins, mCompressorHead,
                        mCompressor, mLauncher, mSoundManager,
                        mLevelManager);
            }
            mWasLeft = false;
            mWasRight = false;
            mWasFire = false;
            mWasUp = false;
            mTrackballDX = 0;
            mTouchFire = false;
            mATSTouchFire = false;
            mATSTouchDX = 0;
        }

        public void cleanUp() {
            synchronized (mSurfaceHolder) {
                // I don't really understand why all this is necessary.
                // I used to get a crash (an out-of-memory error) once every six or
                // seven times I started the game.  I googled the error and someone
                // said you have to call recycle() on all the bitmaps and set
                // the pointers to null to facilitate garbage collection.  So I did
                // and the crashes went away.
                mImagesReady = false;
                boolean imagesScaled = (mBackgroundOrig == mBackground.bmp);
                mBackgroundOrig.recycle();
                mBackgroundOrig = null;
                for (int i = 0; i < mBubblesOrig.length; i++) {
                    mBubblesOrig[i].recycle();
                    mBubblesOrig[i] = null;
                }
                mBubblesOrig = null;
                for (int i = 0; i < mBubblesBlindOrig.length; i++) {
                    mBubblesBlindOrig[i].recycle();
                    mBubblesBlindOrig[i] = null;
                }
                mBubblesBlindOrig = null;
                for (int i = 0; i < mFrozenBubblesOrig.length; i++) {
                    mFrozenBubblesOrig[i].recycle();
                    mFrozenBubblesOrig[i] = null;
                }
                mFrozenBubblesOrig = null;
                for (int i = 0; i < mTargetedBubblesOrig.length; i++) {
                    mTargetedBubblesOrig[i].recycle();
                    mTargetedBubblesOrig[i] = null;
                }
                mTargetedBubblesOrig = null;
                mBubbleBlinkOrig.recycle();
                mBubbleBlinkOrig = null;
                mGameWonOrig.recycle();
                mGameWonOrig = null;
                mGameLostOrig.recycle();
                mGameLostOrig = null;
                mHurryOrig.recycle();
                mHurryOrig = null;
                mPenguinsOrig.recycle();
                mPenguinsOrig = null;
                mCompressorHeadOrig.recycle();
                mCompressorHeadOrig = null;
                mCompressorOrig.recycle();
                mCompressorOrig = null;
                mLifeOrig.recycle();
                mLifeOrig = null;

                if (imagesScaled) {
                    mBackground.bmp.recycle();
                    for (int i = 0; i < mBubbles.length; i++) {
                        mBubbles[i].bmp.recycle();
                    }
                    for (int i = 0; i < mBubblesBlind.length; i++) {
                        mBubblesBlind[i].bmp.recycle();
                    }
                    for (int i = 0; i < mFrozenBubbles.length; i++) {
                        mFrozenBubbles[i].bmp.recycle();
                    }
                    for (int i = 0; i < mTargetedBubbles.length; i++) {
                        mTargetedBubbles[i].bmp.recycle();
                    }
                    mBubbleBlink.bmp.recycle();
                    mGameWon.bmp.recycle();
                    mGameLost.bmp.recycle();
                    mHurry.bmp.recycle();
                    mPenguins.bmp.recycle();
                    mCompressorHead.bmp.recycle();
                    mCompressor.bmp.recycle();
                    mLife.bmp.recycle();
                }
                mBackground.bmp = null;
                mBackground = null;
                for (int i = 0; i < mBubbles.length; i++) {
                    mBubbles[i].bmp = null;
                    mBubbles[i] = null;
                }
                mBubbles = null;
                for (int i = 0; i < mBubblesBlind.length; i++) {
                    mBubblesBlind[i].bmp = null;
                    mBubblesBlind[i] = null;
                }
                mBubblesBlind = null;
                for (int i = 0; i < mFrozenBubbles.length; i++) {
                    mFrozenBubbles[i].bmp = null;
                    mFrozenBubbles[i] = null;
                }
                mFrozenBubbles = null;
                for (int i = 0; i < mTargetedBubbles.length; i++) {
                    mTargetedBubbles[i].bmp = null;
                    mTargetedBubbles[i] = null;
                }
                mTargetedBubbles = null;
                mBubbleBlink.bmp = null;
                mBubbleBlink = null;
                mGameWon.bmp = null;
                mGameWon = null;
                mGameLost.bmp = null;
                mGameLost = null;
                mHurry.bmp = null;
                mHurry = null;
                mPenguins.bmp = null;
                mPenguins = null;
                mCompressorHead.bmp = null;
                mCompressorHead = null;
                mCompressor.bmp = null;
                mCompressor = null;
                mLife.bmp = null;
                mLife = null;

                mImageList = null;
                mSoundManager.cleanUp();
                mSoundManager = null;
                mLevelManager = null;
                mFrozenGame = null;
            }
        }
    }

    private Context mContext;
    private GameThread thread;

    public GameView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //Log.i("frozen-bubble", "GameView constructor");

        mContext = context;
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);

        thread = new GameThread(holder, null, 0);
        setFocusable(true);
        setFocusableInTouchMode(true);

        thread.setRunning(true);
        thread.start();
    }

    public GameView(Context context, byte[] levels, int startingLevel) {
        super(context);
        //Log.i("frozen-bubble", "GameView constructor");

        mContext = context;
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);

        thread = new GameThread(holder, levels, startingLevel);
        setFocusable(true);
        setFocusableInTouchMode(true);

        thread.setRunning(true);
        thread.start();
    }

    public GameThread getThread() {
        return thread;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent msg) {
        //Log.i("frozen-bubble", "GameView.onKeyDown()");
        return thread.doKeyDown(keyCode, msg);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent msg) {
        //Log.i("frozen-bubble", "GameView.onKeyUp()");
        return thread.doKeyUp(keyCode, msg);
    }

    /**
     * 当用户在该组件上触发轨迹球屏事件时触发该方法。(现在的手机基本上没有轨迹球了)
     *
     * @param event
     * @return
     */
    @Override
    public boolean onTrackballEvent(MotionEvent event) {
        //Log.i("frozen-bubble", "event.getX(): " + event.getX());
        //Log.i("frozen-bubble", "event.getY(): " + event.getY());
        return thread.doTrackballEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return thread.doTouchEvent(event);
    }

    /**
     * Activity生命周期中，onStart, onResume, onCreate都不是真正visible的时间点，真正的visible时间点是onWindowFocusChanged()函数被执行时。
     *
     * @param hasWindowFocus
     */
    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        //Log.i("frozen-bubble", "GameView.onWindowFocusChanged()");
        if (!hasWindowFocus) {
            thread.pause();
        }
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width,
                               int height) {
        //Log.i("frozen-bubble", "GameView.surfaceChanged");
        thread.setSurfaceSize(width, height);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        //Log.i("frozen-bubble", "GameView.surfaceCreated()");
        thread.setSurfaceOK(true);
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        //Log.i("frozen-bubble", "GameView.surfaceDestroyed()");
        thread.setSurfaceOK(false);
    }

    /**
     * 清理资源（主要释放各种图片资源）
     */
    public void cleanUp() {
        //Log.i("frozen-bubble", "GameView.cleanUp()");
        thread.cleanUp();
        mContext = null;
    }
}
